﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components.Surface;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Engines.PathSpaceMLT;
using RayDen.RayEngine.Engines.PathTracer;

namespace RayDen.RayEngine.Engines.VertexFramework
{
    public class VFPathSampler : PathSamplerBase
    {
        internal RgbSpectrum Throughput;
        public PurePathTracerPathState PathState;


        protected int depth, tracedShadowRayCount, currentVertex;
        protected float pathWeight;
        protected bool specularBounce;

        SurfaceIntersectionData hitInfo = null;

        public UInt64 PathExpression;

        protected RayEngineScene scene;
        public int MaxRaysPerPath
        {
            get;
            set;
        }

        internal struct ShadowRayInfo
        {
            public float pdf;
            public RgbSpectrum color;
            public RayData shadowRay;
            public int currentShadowRayIndex;
        }
        private ShadowRayInfo[] secRays;
        private PathVertex[] vertices;


        public override void InitPath(IPathProcessor buffer)
        {
            base.InitPath(buffer);
            this.scene = pathIntegrator.Scene;
            this.Radiance = new RgbSpectrum(0f);
            this.currentVertex = 0;
            this.Throughput = new RgbSpectrum(1f);
            this.PathState = PurePathTracerPathState.EyeVertex;
            if (this.secRays == null)
                this.secRays = new ShadowRayInfo[scene.ShadowRayCount];
            if (vertices == null)
                vertices = Enumerable.Range(0, scene.MaxPathDepth*2).Select(i => new PathVertex()).ToArray();
            this.Sample = pathIntegrator.Sampler.GetSample(this.Sample);
            IRay ray;
            pathIntegrator.Scene.Camera.GetRay(Sample.imageX, Sample.imageY, out ray);
            this.PathRay = (RayData)ray;
            this.RayIndex = -1;
            this.pathWeight = 1.0f;
            this.tracedShadowRayCount = 0;
            this.depth = 0;
            this.specularBounce = true;
        }

        public override bool FillRayBuffer(RayBuffer rayBuffer)
        {
            var leftSpace = rayBuffer.LeftSpace();
            if (((PathState == PurePathTracerPathState.EyeVertex) && (1 > leftSpace)) ||
            ((PathState == PurePathTracerPathState.ShadowRaysOnly) && (tracedShadowRayCount > leftSpace)) ||
            ((PathState == PurePathTracerPathState.NextVertex) && (tracedShadowRayCount + 1 > leftSpace)))
                return false;
            if (PathState != PurePathTracerPathState.ShadowRaysOnly)
                RayIndex = rayBuffer.AddRay(ref PathRay);
            if (PathState == PurePathTracerPathState.NextVertex || PathState == PurePathTracerPathState.ShadowRaysOnly)
            {
                for (int i = 0; i < tracedShadowRayCount; ++i)
                    secRays[i].currentShadowRayIndex = rayBuffer.AddRay(ref secRays[i].shadowRay);
            }
            return true;
        }

        public override void Advance(RayBuffer rayBuffer, SampleBuffer consumer)
        {
            int currentTriangleIndex = 0;
#if VERBOSE

            try {
#endif

            base.Advance(rayBuffer, consumer);

            RayHit rayHit = rayBuffer.rayHits[RayIndex];

            depth++;
            bool missed = rayHit.Index == 0xffffffffu;
            if (missed || depth > scene.MaxPathDepth)
            {
                if (missed)
                {
                    vertices[currentVertex].Event = BsdfEvent.Environment;
                    vertices[currentVertex++].ThroughtPut = this.SampleEnvironment(PathRay.Dir);
                }
                Splat(consumer);

                return;
            }

            if (hitInfo == null)
            {
                hitInfo = SurfaceSampler.GetIntersection(ref PathRay, ref rayHit);
            }
            else
            {
                SurfaceSampler.GetIntersection(ref PathRay, ref rayHit, ref hitInfo);
            }
            currentTriangleIndex = (int)rayHit.Index;

            if (hitInfo.IsLight)
            {
                {
                    var lt = scene.GetLightByIndex(currentTriangleIndex);
                    //if (lt != null)
                    {
                        var le = (RgbSpectrum)(RgbSpectrumInfo)(lt.Le(ref PathRay.Dir));
                        vertices[currentVertex].Event = BsdfEvent.Light;
                        vertices[currentVertex++].ThroughtPut = le;
                    }
                }
                Splat(consumer);

                return;
            }

            var hitPoint = PathRay.Point(rayHit.Distance);
            vertices[currentVertex].HitPoint = hitPoint;

            Vector wo = -PathRay.Dir;
            tracedShadowRayCount = 0;

            float fPdf;
            Vector wi;
            BsdfEvent @evt;
            RgbSpectrum f = hitInfo.MMaterial.Sample_f(ref wo, out wi, ref hitInfo.Normal, ref hitInfo.ShadingNormal, ref Throughput,
                                                      Sample.GetLazyValue(), Sample.GetLazyValue(),
                                                      Sample.GetLazyValue(), ref hitInfo.TextureData,
                                                      out fPdf, out @evt);
            specularBounce = evt.Has(BsdfEvent.Specular);
            if ((fPdf <= 0.0f) || f.IsBlack())
            {
                if (tracedShadowRayCount > 0)
                    PathState = PurePathTracerPathState.ShadowRaysOnly;
                else
                {
                    vertices[currentVertex].Event = BsdfEvent.Absorb;
                    Splat(consumer);
                }
                return;
            }
            pathWeight *= fPdf;
            Throughput *= (f * hitInfo.Color) / fPdf;
            vertices[currentVertex].HitPoint = hitPoint;
            vertices[currentVertex].Wi = wi;
            vertices[currentVertex].Wo = wo;
            vertices[currentVertex].ShadeNormal = hitInfo.ShadingNormal;
            vertices[currentVertex].TriangleIndex = currentTriangleIndex;
            vertices[currentVertex].Bsdf = hitInfo.MMaterial;
            vertices[currentVertex].Event = @evt;
            vertices[currentVertex].BsdfSample = f;
            vertices[currentVertex].BsdfWeight = fPdf;
            vertices[currentVertex].ThroughtPut = (f * hitInfo.Color) / fPdf;

            if (depth > scene.MaxPathDepth)
            {
                float prob = Math.Max(Throughput.Filter(), scene.RussianRuletteImportanceCap);
                if (prob >= Sample.GetLazyValue())
                {
                    Throughput /= prob;
                    vertices[currentVertex].ThroughtPut/=prob;
                    vertices[currentVertex].RRProbability = 1f / prob;
                    pathWeight *= prob;
                }
                else
                {
                    if (tracedShadowRayCount > 0)
                        PathState = PurePathTracerPathState.ShadowRaysOnly;
                    else
                    {
                        vertices[currentVertex].Event = BsdfEvent.Absorb;

                        Splat(consumer);

                    }

                    return;
                }
            }

            PathRay.Org = hitPoint;
            PathRay.Dir = wi.Normalize();
            PathState = PurePathTracerPathState.NextVertex;
            currentVertex++;
#if VERBOSE
            }
            catch (Exception ex) {
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Advance path exception");
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Error triangle {0}", currentTriangleIndex);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.Message);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.StackTrace);

            }
#endif
        }

        public override void Splat(SampleBuffer sampleBuffer)
        {
            var thr = new RgbSpectrum(1f);
            for (int i = 0; i < currentVertex; i++)
            {
                switch (vertices[i].Event)
                {
                    //case BsdfEvent.Absorb:
                    case BsdfEvent.Light:
                    case BsdfEvent.Environment:
                            Radiance += thr*vertices[i].ThroughtPut;

                        goto absorb;
                    default:
                        if (!vertices[i].ThroughtPut.IsBlack())
                            thr *= vertices[i].ThroughtPut*vertices[currentVertex].RRProbability;
                        //else thr *= vertices[i].ThroughtPut * vertices[currentVertex].RRProbability;
                        break;
                }
            }

            
            @absorb:

            if (!this.Radiance.IsBlack())
                sampleBuffer.SplatSample(this.Sample.imageX, this.Sample.imageY, ref this.Radiance);
            InitPath(this.pathIntegrator);
        }

        private RgbSpectrum SampleEnvironment(Vector vector)
        {
            return this.scene.SampleEnvironment(-vector);
        }
    }
}
